有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

spring Java泛型和枚举,模板参数丢失

我有一个相当复杂的结构,它没有按预期工作。我就是这么做的:

public interface ResultServiceHolder {
    <M, ID extends Serializable, BO extends BusinessObject<M, ID>> ResultService<M, ID, BO> getService();
}

public enum ResultTypes implements ResultServiceHolder {
    RESULT_TYPE_ONE {
        @Override
        public ResultOneService getService() { //unchecked conversion?
            return serviceInitializer.getResultOneService();
        }
    },
    RESULT_TYPE_TWO {
        @Override
        public ResultTwoService getService() {  //unchecked conversion?
            return serviceInitializer.getResultTwoService();
        }
    },
    RESULT_TYPE_THREE {
        @Override
        public ResultThreeService getService() {  //unchecked conversion?
            return serviceInitializer.getResultThreeService();
        }
    };

    protected ServiceInitializer serviceInitializer;


    protected void setServiceInitializer(ServiceInitializer serviceInitializer) {
        this.serviceInitializer = serviceInitializer;
    }

    @Component
    public static class ServiceInitializer {
        @Autowired
        private ResultOneService resultOneService;

        @Autowired
        private ResultTwoService resultTwoService;

        @Autowired
        private ResultThreeService resultThreeService;

        @PostConstruct
        public void init() {
            for(ResultTypes resultType : ResultTypes.values()) {
                resultType.setServiceInitializer(this);
            }
        }

        //getters
    }
}

其目的是基于枚举来泛化调用,而只是能够在枚举数组上进行迭代

    for(ResultServiceHolder resultServiceHolder : ResultTypes.values()) {
        if(resultServiceHolder.equals(post.getPostResultTypeCode())) {
            return resultServiceHolder.getService().createResultSearchCriteriaResponse(postId);
        }
    }

这很好,很好用。然而,如果我说

ResultTypes.RESULT_TYPE_ONE.getService().getRepository()

然后它是一个BaseRepository<Object, Serializable>而不是一个BaseRepository<ResultTypeOne, Long>。方法resultTypeHolder.getService()返回ResultService<M, ID, BO>,但最终变成ObjectSerializable

我做错了什么如何保留通用参数类型

我想补充一点,是的,我确实意识到问题在于未经检查的铸造。但这些服务被定义为

public interface ResultTypeOneService
    extends ResultService<ResultTypeOne, Long, ResultTypeOneBO> {
}

我不知道为什么不能推断出这些类型

编辑:从技术上讲,如果我明确推断出:

ResultTypes.RESULT_TYPE_ONE.<ResultTypeOne, Long, ResultTypeOneBO>getService().getRepository()

但它应该是自动的,为什么不能自动工作呢?我应该为它提供某种包含类型的对象吗?为什么返回类型不足以实现这一点

EDIT2:的超类是

@SuppressWarnings("serial")
@EntityListeners(EntityListener.class)
@MappedSuperclass
public abstract class EntityBase implements Serializable {

但它没有映射到边界中的任何位置

EDIT3:非常感谢@Radiodef!理论上的解决方案是这样的,而且效果非常好:

public interface ResultServiceHolder<M, ID extends Serializable, BO extends BusinessObject<M, ID>> {
    ResultService<M, ID, BO> getService();
}

public abstract class ResultTypes<M, ID extends Serializable, BO extends BusinessObject<M, ID>>
    implements ResultServiceHolder<M, ID, BO> {

    public static ResultTypes<?, ?, ?>[] values() {
        return new ResultTypes<?, ?, ?>[] {RESULT_ONE, RESULT_TWO, RESULT_THREE};
    }

    public static final ResultTypes<ResultOne, Long, ResultOneBO> RESULT_ONE = new ResultTypes<ResultOne, Long, ResultOneBO>("Result One") {
        @Override
        public ResultOneService getService() {
            return serviceInitializer.resultOneService;
        }
    };
    public static final ResultTypes<ResultTwo, Long, ResultTwoBO> RESULT_TWO = new ResultTypes<ResultTwo, Long, ResultTwoBO>("Result Two") {
        @Override
        public ResultTwoService getService() {
            return serviceInitializer.resultTwoService;
        }
    };
    public static final ResultTypes<ResultThree, Long, ResultThreeBO> RESULT_THREE = new ResultTypes<ResultThree, Long, ResultThreeBO>("Result Three") {
        @Override
        public ResultThreeService getService() {
            return serviceInitializer.resultThreeService;
        }
    };

    protected String name;

    protected ServiceInitializer serviceInitializer;

    private ResultTypes(String name) {
        this.name = name;
    }

    protected void setServiceInitializer(ServiceInitializer serviceInitializer) {
        this.serviceInitializer = serviceInitializer;
    }

    @Component
    static class ServiceInitializer {
        @Autowired
        private ResultOneService resultOneService;

        @Autowired
        private ResultTwoService resultTwoService;

        @Autowired
        private ResultThreeService resultThreeService;

        @PostConstruct
        public void init() {
            for (ResultTypes resultType : ResultTypes.values()) {
                resultType.setServiceInitializer(this);
            }
        }
    }
}

我认为,由于解决方案会变得很长,我将坚持enum方法,并接受这种边界损失。我必须添加自己的values()实现,这比强制执行这些边界所获得的更多。然而,这是一个有趣的理论练习,再次感谢您的帮助


共 (2) 个答案

  1. # 1 楼答案

    好的,首先你需要理解为什么你正在做的可能不是你认为它正在做的。让我们看一个更简单的例子

    interface Face {
        <T> List<T> get();
    }
    

    这里有一个通用方法,get。泛型方法的类型参数取决于调用站点提供的内容。例如:

    Face f = ...;
    // this call site dictates T to be Number
    List<Number> l = f.<Number>get();
    

    当你像这样覆盖它的时候

    class Impl implements Face {
        @Override
        public List<String> get() { return ...; }
    }
    

    这是你能够做的事情(只是因为擦除),但你可能不应该做。它只允许向后兼容非泛型代码。你应该听从警告,而不是去做。这样做意味着,例如,我仍然可以过来,命令它返回其他东西:

    Face f = new Impl();
    // now I've caused heap pollution because you
    // actually returned to me a List<String>
    List<Number> l = f.<Number>get();
    

    这就是为什么存在未经检查的转换

    您可能的意思是使用通用接口声明:

    interface Face<T> {
        List<T> get();
    }
    

    现在T的参数取决于对象引用的类型

    Face<Number> f = ...;
    // get must return List<Number>
    List<Number> l = f.get();
    

    我们可以像这样实施它

    class Impl implements Face<String> {
        @Override
        public List<String> get() { return ...; }
    }
    

    此外,不能访问枚举上的协变返回类型。重写枚举常量上的方法时,其类是匿名的。匿名类没有名称,无法引用。因此,程序员无法知道它的协变返回类型来使用它。此外,枚举不能声明泛型类型参数。因此,您想要做的事情在enum中根本不可能实现

    可以使用带有public static final实例的类来模拟通用枚举:

    public abstract class SimEnum<T> implements Face<T> {
        public static final SimEnum<Number> A = new SimEnum<Number>() {
            @Override
            public List<Number> get() { return ...; }
        };
        public static final SimEnum<String> B = new SimEnum<String>() {
            @Override
            public List<String> get() { return ...; }
        };
    
        private SimEnum() {}
    
        public static SumEnum<?>[] values() {
            return new SimEnum<?>[] { A, B };
        }
    }
    

    否则你需要彻底改变你的想法

  2. # 2 楼答案

    可能使用接口/抽象类而不是枚举

    枚举不能有类型参数,但类和接口可以

    例如

    接口

    实体。java

    “东西”界面

    import java.io.Serializable;
    
    public interface Entity<K extends Serializable> {
        // TODO: Put entity type things here!
        // for example, things like "K getId();"
        // You may want an abstract base class for this interface that all Entitys extend
    }
    

    存储库。java

    积垢和东西混在一起

    import java.io.Serializable;
    
    public interface Repository<K extends Serializable, V extends Entity<K>> {
        V getValue(K key);
        // Other CRUD stuff
    }
    

    服务。java

    一个服务负责处理一些事情

    public interface Service<K, V> {
        // Could have an abstract service class that has a repository and implements this for you...
        V get(K key);
        // Other "generic service" type stuff
    
    }
    

    实体类

    实体1。java

    带字符串键的坚实基类

    public class Entity1 implements Entity<String> {
        // TODO implement Entity stuff...
    }
    

    实体2。java

    带整数键的实心基类

    public class Entity2 implements Entity<Integer> {
        // TODO implement methods...
    }
    

    实体1服务。java

    实体1服务

    public class Entity1Service implements Service<String, Entity1> {
    
        // Would not have to implement this if you extended an abstract base Service class
        @Override
        public Entity1 get(String key) {
            return null;
        }
    
    }
    

    实体2服务。java

    实体2服务

    public class Entity2Service implements Service<Integer, Entity2> {
    
        // Wouldn't need this if you had abstract Service class either...
        @Override
        public Entity2 get(Integer key) {
            return null;
        }
    
    }
    

    服务持有者。java

    不是一个枚举,而是一个接口——你可以在这里添加一些方法来设置spring中的“服务”等等

    import java.io.Serializable;
    
    public abstract class ServiceHolder<K extends Serializable, V, S extends Service<K, V>> {
    
        public static final ServiceHolder<String, Entity1, Entity1Service> ENTITY_1_SERVICE = new ServiceHolder<String, Entity1, Entity1Service>() {};
        public static final ServiceHolder<Integer, Entity2, Entity2Service> ENTITY_2_SERVICE = new ServiceHolder<Integer, Entity2, Entity2Service>() {};
    
        private S service;
    
        private ServiceHolder() {
        }
    
        public S getService() {
            return service;
        }
    
        public void setService(S service) {
            this.service = service;
        }
    }
    

    有趣的部分

    我想这是你想要的东西,如果我误解了,请告诉我

    public class PleaseCompile {
    
        public static void main(String[] args) {
            Entity1 solid1 = ServiceHolder.ENTITY_1_SERVICE.getService().get("[KEY]");
            Entity2 solid2 = ServiceHolder.ENTITY_2_SERVICE.getService().get(42);
    
            ...
        }
    }
    

    希望这有帮助